#include "sockets_imp.hpp"
#include <cassert>
#include <cstdlib>

using namespace std;

TcpSocket::TcpSocket( bool blocking )
{
	data = new SocketData();
	data->handler = fromNative( ::socket(PF_INET, SOCK_STREAM, 0) );
	if ( !isValid() )
	{
		delete data;
		throw SocketException( getsyserror() );
	}
}

TcpSocket::TcpSocket( TcpSocket const& tcpSocket ) : data(tcpSocket.data)
{
	data->refCount++;
}

TcpSocket::~TcpSocket()
{
	try 
	{	
		if ( data->refCount > 0 ) --data->refCount;
		else
		{
			close();
			delete data;
		}
	}catch (...) // Glip told me to ignore >:O
	{} 
}

void TcpSocket::connect( NetAddress& addr )
{
	int r = ::connect(toNative(data->handler), reinterpret_cast<sockaddr*>(&addr.data), sizeof(struct sockaddr));
	
	if( isError(r) )
	{
		int err = getsyserror();
		
		switch (err)
		{
			case SCKER_INPROGRESS:
			case SCKER_WOULDBLOCK:
			data->setFlags( SocketData::Flags::Connecting );
			data->clearFlags( SocketData::Flags::Connected );
			return;
			
			default:
			throw SocketException(err);
		}
	}
	
	assert( r == 0 );
	
	data->setFlags( SocketData::Flags::Connected );
	data->clearFlags( SocketData::Flags::Connecting );
}

TcpSocket TcpSocket::accept( NetAddress& addr )
{
	socklen_t sin_size = sizeof(sockaddr_in);
	
	SocketData* newdata = new SocketData();
	newdata->handler = fromNative( ::accept(toNative(data->handler), reinterpret_cast<sockaddr *>(&addr.data), &sin_size) );
	
	TcpSocket newSocket( newdata );
	
	if ( !newSocket.isValid() )
	{
		int err = getsyserror();
		switch ( err )
		{
			case SCKER_INPROGRESS:
			case SCKER_WOULDBLOCK:
			break;
			
			default:
			throw SocketException(err);
		}
	}else
	{
		newSocket.data->setFlags( SocketData::Flags::Connected );
	}
	
	return newSocket;
}

void TcpSocket::listen( int port )
{
	sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons((u_short)port);
	addr.sin_addr.s_addr = INADDR_ANY;
	memset(reinterpret_cast<char *>(addr.sin_zero), 0, sizeof(addr.sin_zero));

	if ( isError( ::bind( toNative( data->handler ), reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) ) )
	{
		data->setFlags( SocketData::Flags::ErrorOccurred );
		throw SocketException( getsyserror() );
	}
	
	int ret = ::listen(toNative(data->handler), 5); // LOL HARDCODED
	if ( isError( ret ) )
	{
		throw SocketException( getsyserror() );
	}
}

bool TcpSocket::isValid() const
{
	return toNative( data->handler ) != INVALID_SOCKET;
}

bool TcpSocket::isConnected() const
{
	return (data->flags & SocketData::Flags::Connected);
}

void TcpSocket::close()
{
	if( isValid() )
	{
		if ( isError( closeSocket( data->handler ) ) ) throw SocketException( getsyserror() );
		data->handler = fromNative( INVALID_SOCKET );
		data->clearFlags( SocketData::Flags::Connecting | SocketData::Flags::Connected );
	}
}

int TcpSocket::send( char const * buffer, int length )
{
	int result = ::send( toNative(data->handler), buffer, length, 0);
	if ( isError(result) )
	{
		int err = getsyserror();
		switch ( err )
		{
			case SCKER_INPROGRESS:
			case SCKER_WOULDBLOCK:
			break;
			
			default:
			data->setFlags( SocketData::Flags::ErrorOccurred );
			data->clearFlags( SocketData::Flags::Connected | SocketData::Flags::Connecting );
		}
		return 0;
	}
	return result;
}

int TcpSocket::recv( char* buffer, int length )
{
	int result = ::recv( toNative(data->handler), buffer, length, 0);
	if ( isError(result) )
	{
		int err = getsyserror();
		switch ( err )
		{
			case SCKER_INPROGRESS:
			case SCKER_WOULDBLOCK:
			break;
			
			default:
			data->setFlags( SocketData::Flags::ErrorOccurred );
			data->clearFlags( SocketData::Flags::Connected | SocketData::Flags::Connecting );
		}
		return 0;
	}else if ( result == 0 )
	{
		data->clearFlags( SocketData::Flags::Connected | SocketData::Flags::Connecting );
	}
	return result;
}

bool TcpSocket::waitForData( int msec )
{
	//if ( !( data->flags & SocketData::Flags::Connected ) )
		//return false;
	
	fd_set monitor;
	FD_ZERO(&monitor);
	FD_SET(toNative(data->handler), &monitor);
		
	if ( msec < 0 )
	{
		#ifdef SCK_WIN32
		select(0, &monitor, 0, 0, 0);
		#else
		select(toNative(data->handler)+1, &monitor, 0, 0, 0);
		#endif
	}else
	{
		timeval tv;
		tv.tv_sec = msec / 1000;
		tv.tv_usec = (msec * 1000) % 1000000;
		#ifdef SCK_WIN32
		select(0, &monitor, 0, 0, &tv);
		#else
		select(toNative(data->handler)+1, &monitor, 0, 0, &tv);
		#endif
	}
	
	if(FD_ISSET(toNative(data->handler), &monitor))
		return true;
		
	return false;
}
